package com.zmy.rxcache

import android.text.TextUtils
import android.util.Log
import com.google.gson.Gson
import com.google.gson.internal.`$Gson$Types`.getRawType
import com.google.gson.stream.JsonReader
import io.reactivex.Flowable
import io.reactivex.Maybe
import io.reactivex.Observable
import io.reactivex.Single
import java.io.File
import java.lang.reflect.*

class RxCache : InvocationHandler {


    val TAG = "RxCache"

    var cacheDir: File

    private lateinit var service: Any

    constructor(builder: Builder) {
        this.cacheDir = builder.cacheDir ?: throw IllegalStateException("not set cache dir ")

    }


    fun <T> create(service: Any, serviceClass: Class<T>): T {

        this.service = service
        return Proxy.newProxyInstance(
            service::class.java.classLoader,
            arrayOf(serviceClass),
            this
        ) as T
    }


    override fun invoke(proxy: Any?, method: Method, args: Array<out Any>?): Any? {

        val annotation = method.getAnnotation(Cache::class.java)

        val returnType = method.genericReturnType

        if (annotation != null) {
            var dataType: Type
            var type = TypeUtils.get(returnType)
                ?: throw  IllegalStateException("unsupported type ${method.returnType}")

            var needReply = false
            if (TypeUtils.getRawType(type) == Reply::class.java) {
                dataType = TypeUtils.getParameterUpperBound(0, type as ParameterizedType?)
                needReply = true
            } else dataType = type

            val localCacheData: Any? = findLocalCacheForDataKey(
                annotation.key,
                //获取实际的类型
                dataType
            )

            val clazz = when {

                returnType is ParameterizedType -> returnType.rawType

                returnType is Class<*> -> type
                else -> throw  java.lang.IllegalStateException("$type some err happened")
            }

            val response = if (needReply) Reply(localCacheData, DISK) else localCacheData

            //only use  cached local data
            if (localCacheData != null) {
                if (clazz == Flowable::class.java)
                    return Flowable.just(response)
                else if (clazz == Single::class.java)
                    return Single.just(response)
                else if (clazz == Observable::class.java)
                    return Observable.just(response)
                else if (clazz == Maybe::class.java)
                    return Maybe.just(response)
                else throw java.lang.IllegalStateException("unsupported type $clazz")

            } else {
                //cache data
                return loadAndCache(needReply, method, annotation.key, service, args, type)
            }

        }

        val rxSource = method.invoke(service, args)

        return rxSource
    }


    private fun loadAndCache(
        replyWrap: Boolean,
        method: Method,
        key: String,
        upstream: Any,
        args: Array<out Any>?,
        type: Type
    ): Any? {
        Log.i(TAG, "loadAndCache: $type")


        val rxSource = method.invoke(upstream, *(args ?: arrayOfNulls<Any>(0)))

        val sourceEmitter: Observable<*>


        when {
            isObservable(rxSource) -> {
                val observable = rxSource as Observable<*>
                return observable.flatMap { t ->

                    val jsonString = Gson().toJson(t)
                    val file = File(cacheDir, "$key.json")
                    writeToFile(file, jsonString)
                    return@flatMap Observable.just(if (replyWrap) Reply(t, REMOTE) else t)
                }
            }
            isFlowable(rxSource) -> {
                val flowable = rxSource as Flowable<*>
                return flowable.flatMap { t ->
                    val jsonString = Gson().toJson(t)
                    val file = File(cacheDir, "$key.json")
                    writeToFile(file, jsonString)
                    return@flatMap Flowable.just(if (replyWrap) Reply(t, REMOTE) else t)
                }
            }
            isSingle(rxSource) -> {

                val single = rxSource as Single<*>
                return single.flatMap { t ->
                    val jsonString = Gson().toJson(t)
                    val file = File(cacheDir, "$key.json")
                    writeToFile(file, jsonString)
                    return@flatMap Single.just(t)
                }
            }
            isMaybe(rxSource) -> {

                val maybe = rxSource as Maybe<*>

                return maybe.flatMap { t ->
                    val jsonString = Gson().toJson(t)
                    val file = File(cacheDir, "$key.json")
                    writeToFile(file, jsonString)
                    return@flatMap Maybe.just(t)
                }
            }

            else -> throw  java.lang.IllegalStateException("unsupported type $rxSource")
        }


    }


    fun isFlowable(source: Any) = isSubtypeOf(source, Flowable::class.java)

    fun isMaybe(source: Any) = isSubtypeOf(source, Maybe::class.java)

    fun isSingle(source: Any) = isSubtypeOf(source, Single::class.java)

    fun isObservable(source: Any) = isSubtypeOf(source, Observable::class.java)

    private fun isSubtypeOf(obj: Any, superType: Class<*>) =
        superType.isAssignableFrom(obj.javaClass)

    private fun writeToFile(file: File, jsonString: String?) {

        if (TextUtils.isEmpty(jsonString).not()) {
            if (file.exists().not()) {
                file.createNewFile()
                val outputStream = file.outputStream().buffered()
                outputStream.write(jsonString!!.toByteArray())
                outputStream.flush()
                outputStream.close()

            }

        }
    }

    private fun findLocalCacheForDataKey(key: String, type: Type): Any? {

        val listFiles = cacheDir.listFiles()

        listFiles.forEach {

            if (it.name.startsWith(key)) {
                Log.i(TAG, "findLocalCacheForDataKey: $key.json $$$$$$$$$$ cache success")
                return Gson().fromJson(JsonReader(it.reader()), type)
            }
        }

        return null

    }


    interface Interceptor {

    }

    class Builder {

        var cacheDir: File? = null

        fun setCacheDir(cacheDir: File): Builder {

            this.cacheDir = cacheDir
            return this
        }


        fun build(): RxCache {

            return RxCache(this)
        }
    }


}